package 线程2;
//线程安全 -- 这是多线程章节中最核心的话题,也是最难的话题,也适合工作中最相关的话题.
//多线程并不好驾驭.正因为如此,有的编程语言,直接就把线程给干掉了(进行了诸多限制)
//例如,Python中的线程,就是"假的线程",很多时候根本无法并发
//JS中压根就没线程,只能通过"定时器"+回调 这样的机智来凑合实现类似于"并发"的效果
//Go中摒弃了线程,但是引入了更高端的"协程",接住协程来并发编程'

/**
 *  当前代码,预期能够自增10w次
 *  实际上自增的次数,无法确定.每次运行结果都不一样
 *  写代码,最害怕的就是不确定
 *  显然,这个代码应该视为bug
 */

/**
 * 由于多线程并发执行,导致代码中出现bug,这样的情况就称为"线程不安全"
 *
 * 分析程序执行的过程:
 * count++详细的过程:分为三个步骤
 * --
 * (CPU负责运算)
 */

/**
 * 线程安全~
 * 产生线程不安全的原因:
 * 1.线程之间是抢占式执行.[根本原因,万恶之源]
 *   抢占式执行,导致两个线程里面操作的先后顺序无法确定~
 *   这样的随机性,就是导致线程安全问题的根本所在.[我们无力改变,操作系统内核实现的]
 * 2.多个线程修改同一个变量[和代码的写法密切相关]
 *  一个线程修改同一个变量,没有线程安全问题!!不涉及并发,结果就是确定
 *  多个线程读取同一个变量,也没有线程安全问题!!(读只是单纯把数据从内存放到CPU中~,不管怎么读,内存的数据始终不变~2)
 *  多个线程修改不同变量,也没有线程安全问题!!其实就认为就类似于第一种情况.
 *
 *  所以为了规避线程安全问题,就可以尝试变幻代码的组织形式,达到一个线程只改一个变量(得看场景,特地情况下才可能做到)
 *
 * 3.原子性~
 * 像++这样的操作,本质上是三个步骤,是一个"非原子"的操作
 * 想 = 操作,本质上是一个步骤,认为是一个"原子"的操作
 *
 * 像当前,咱们的++操作本身不是原子的.可以通过加锁的方式,把这个操作变成原子的.
 *
 * 4.内存可见性
 *  和原子性有些类似~
 *   例如,考虑一个更简单的场景:
 *   线程1,读取变量
 *   线程2 对变量自增
 */
public class TestDemo8 {
    static class Counter{
        public int count = 0;
        public void increase(){
            count++;
        }
    }
    static Counter counter = new Counter();//这里加个static有啥作用?

    public static void main(String[] args) {
    //此处创建两个线程,分别对count自增5w次
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.increase();
                }
            }
        };
        t1.start();

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.increase();
                }
            }
        };
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.count);

    }
}
